#include <amxmodx>
#include <amxmisc>
#include <nvault>

#define MM_GLOBALS_INCLUDED 1
#pragma semicolon 1

#define MAX_NAME_LEN 32
#define MAP_NAME_LEN 32
#define MAX_PLAYERS 33
#define MAX_MAPS 5

#define MAP_NOT_PRESENT -1
#define MAP_NOT_ENOUGH_PLAYERS -2
#define MAP_TOO_MANY_PLAYERS -3
#define MAP_LAST_PLAYED -4
#define MAP_CURRENT -5
#define MAP_VALID_FOR_NOMINATION 1

#define PLAYER_DID_NOT_VOTE 100
#define PLAYER_VOTE_EXIT 101

#define TASK_MAP_CHANGE 1000
#define TASK_TIMELEFT_CLOCK 1033

#define NO_MAP -1

#define DEBUG 1

//{ CVARS
new gCvarActive;
new gCvarInfo;
new gCvarInfoDelay;
new gCvarTimeVoice;
new gCvarUseTheTime;
new gCvarUseTimeleft;
new gCvarUseNextmap;
new gCvarUseCurrentMap;
new gCvarUseFF;
new gCvarShowTimeleft;

new gCvarMP_TIMELIMIT;
new gCvarMP_ROUNDTIME;
new gCvarMP_CHATTIME;
new gCvarMP_FRIENDLYFIRE;

new gAllMapsCount;
new gVault;
new gMapVoteStarted;
new Float:gMP_TIMELIMIT;
new gMP_CHATTIME;
new const mmLogFile[] = "mm_logs.log";
new const mmKeyMapsCount[] = ";MAPS_COUNT";
new const mapManagementVault[] = "mapmanagement";
new gNextMap[MAP_NAME_LEN];

//}
new const keyLastPlayed[] = ";LAST_MAP_";
new const gMapPrefix[][] = {
	"de_",
	"cs_",
	"as_",
	"fy_",
	"he_",
	"aim_",
	"awp_",
	"fun_",
	"as_"
};
new const gMapPrefixCount = 9;
new Float:roundStartTime;
new isMapChanging;

new gHudCenteredMsg;

stock plugin_init_globals(){
	register_dictionary("timeleft.txt");
	register_dictionary("mapmanagement.txt");
	
	register_clcmd("say", "handleSayGlobals");
	
	
	gVault = nvault_open(mapManagementVault);
	gCvarActive = register_cvar("mm_active", "1");
	gCvarInfo = register_cvar("mm_info", "1");
	gCvarInfoDelay = register_cvar("mm_info_delay", "30.0");
	
	gCvarTimeVoice = register_cvar("mm_timevoice", "1");
	gCvarUseTheTime = register_cvar("mm_use_thetime", "1");
	gCvarUseTimeleft = register_cvar("mm_use_timeleft", "1");
	gCvarUseNextmap = register_cvar("mm_use_nextmap", "1");
	gCvarUseCurrentMap = register_cvar("mm_use_currentmap", "1");
	gCvarUseFF = register_cvar("mm_use_ff", "1");
	gCvarShowTimeleft = register_cvar( "mm_show_timeleft", "1");
	gHudCenteredMsg = CreateHudSyncObj();
	
}
//{ Timeleft, thetime, current map, ff, nextmap, ip
public handleSayGlobals(id){
	if(!get_pcvar_num(gCvarActive))
		return PLUGIN_CONTINUE;
	
	new said[40];
	read_args(said, charsmax(said));
	remove_quotes(said);
	if(get_pcvar_num(gCvarUseTheTime) && equali(said, "thetime")){
		sayTheTime(id);
		return PLUGIN_HANDLED;
	}
	if(get_pcvar_num(gCvarUseTimeleft) && equali(said, "timeleft")){
		sayTimeLeft(id);
		return PLUGIN_HANDLED;
	}
	if(get_pcvar_num(gCvarUseNextmap) && equali(said, "nextmap")){
		sayNextMap(id);
		return PLUGIN_HANDLED;
	}
	if(get_pcvar_num(gCvarUseCurrentMap) && equali(said, "currentmap")){
		sayCurrentMap(id);
		return PLUGIN_HANDLED;
	}
	if(get_pcvar_num(gCvarUseFF) && equali(said, "ff")){
		sayFFStatus(id);
		return PLUGIN_HANDLED;
	}
	
	return PLUGIN_CONTINUE;
}


public sayTheTime(id) {
	if(!get_pcvar_num(gCvarActive))
		return PLUGIN_CONTINUE;
		
	if (get_pcvar_num(gCvarTimeVoice))
	{
		new mhours[6], mmins[6], whours[32], wmins[32], wpm[6];
		
		get_time("%H", mhours, 5);
		get_time("%M", mmins, 5);
		
		new mins = str_to_num(mmins);
		new hrs = str_to_num(mhours);
		
		if (mins)
			num_to_word(mins, wmins, 31);
		else
			wmins[0] = 0;
		
		if (hrs < 12)
			wpm = "am ";
		else
		{
			if (hrs > 12) hrs -= 12;
			wpm = "pm ";
		}

		if (hrs) 
			num_to_word(hrs, whours, 31);
		else
			whours = "twelve ";
		
		client_cmd(id, "spk ^"fvox/time_is_now %s_period %s%s^"", whours, wmins, wpm);
	}
	
	new ctime[64];
	
	get_time("%m/%d/%Y - %H:%M:%S", ctime, 63);
	client_print(0, print_chat, "%L:   %s", LANG_PLAYER, "THE_TIME", ctime);
	
	return PLUGIN_HANDLED;
}

stock setTimeVoice(text[], len, flags, tmlf){
	new temp[7][32];
	new secs = tmlf % 60;
	new mins = tmlf / 60;
	
	for (new a = 0;a < 7;++a)
		temp[a][0] = 0;

	if (secs > 0)
	{
		num_to_word(secs, temp[4], 31);
		
		if (!(flags & 8)) 
			temp[5] = "seconds ";	/* there is no "second" in default hl */
	}
	
	if (mins > 59)
	{
		new hours = mins / 60;
		
		num_to_word(hours, temp[0], 31);
		
		if (!(flags & 8))
			temp[1] = "hours ";
		
		mins = mins % 60;
	}
	
	if (mins > 0)
	{
		num_to_word(mins, temp[2], 31);
		
		if (!(flags & 8))
			temp[3] = "minutes ";
	}
	
	if (!(flags & 4))
		temp[6] = "remaining ";
	
	return format(text, len, "spk ^"vox/%s%s%s%s%s%s%s^"", temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6]);
}

public sayTimeLeft(id){
	if (!isMapChanging && get_pcvar_float(gCvarMP_TIMELIMIT))
	{
		new a = get_timeleft();
		
		if (get_pcvar_num(gCvarTimeVoice))
		{
			new svoice[128];
			setTimeVoice(svoice, 127, 0, a);
			client_cmd(id, "%s", svoice);
		}
		client_print(0, print_chat, "%L:  %d:%02d", LANG_PLAYER, "TIME_LEFT", (a / 60), (a % 60));
	}
	else
		client_print(0, print_chat, "%L", LANG_PLAYER, "NO_T_LIMIT");
	
	return PLUGIN_HANDLED;
}

public sayNextMap(id){
	if(gNextMap[0]){
		client_print(id, print_chat, "%L: %s", LANG_PLAYER, "NEXTMAP_IS", gNextMap);
	} else {
		client_print(id, print_chat, "%L", LANG_PLAYER, "NO_NEXTMAP");
	}
}


public sayFFStatus(id){
	client_print(id, print_chat, "FriendlyFire: %s", get_pcvar_num(gCvarMP_FRIENDLYFIRE) ? "ON" : "OFF");
}

public sayCurrentMap(id){
	new map[MAP_NAME_LEN];
	get_mapname(map, MAP_NAME_LEN -1);
	client_print(id, print_chat, "%L", LANG_PLAYER, "CURRENT_MAP", map);
}
//}

stock setTimeleftClockTask(){	
	if(get_pcvar_num(gCvarShowTimeleft)){
		if(!task_exists(TASK_TIMELEFT_CLOCK)){
			set_task(1.0, "showTimeLeft",  TASK_TIMELEFT_CLOCK,_,_,"b");
		}
	} else {
		if(task_exists(TASK_TIMELEFT_CLOCK)){
			remove_task(TASK_TIMELEFT_CLOCK);
		}
	}
}

public showTimeLeft(){
	new timeleft = get_timeleft();
	if(timeleft > 0){
		set_hudmessage(255,255,255,0.75,0.05,0, 1.0, 1.0, 0.1, 0.2, 13);
		show_hudmessage(0,"Time remaining: %d:%02d",timeleft / 60, timeleft % 60);
	}
	return PLUGIN_CONTINUE;
} 

stock roundStart_globals() {
	roundStartTime = get_gametime();
	setTimeleftClockTask();
}
stock checkMapChange(){
	if(isLastRound() && !isMapChanging){
		changeMapInFive();
	}
}
stock plugin_cfg_globals(){
	new cfg[32];
	get_configsdir(cfg,charsmax(cfg));
	server_cmd("exec %s/mm_config.cfg", cfg);
	gCvarMP_TIMELIMIT = get_cvar_pointer("mp_timelimit");
	gCvarMP_ROUNDTIME = get_cvar_pointer("mp_roundtime");
	gCvarMP_FRIENDLYFIRE = get_cvar_pointer("mp_friendlyfire");
	gMP_TIMELIMIT = get_pcvar_float(gCvarMP_TIMELIMIT);
	gCvarMP_CHATTIME = get_cvar_pointer("mp_chattime");
	gMP_CHATTIME = get_pcvar_num(gCvarMP_CHATTIME);
}

stock speakNum(num){
	new secWord[10];
	num_to_word(num, secWord, charsmax(secWord));
	client_cmd(0, "spk fvox/%s.wav", secWord);
}
public  countDown(count){
	if(count == 0)
		return;
	new secWord[10];
	num_to_word(count, secWord, charsmax(secWord));
	client_cmd(0, "spk fvox/%s.wav", secWord);
	set_task(1.0, "countDown", count -1);
}

public speakTimeToChoose(delay){
	if(delay ){
		speakNum(delay);
		set_task(1.0, "speakTimeToChoose", delay -1);
		
	} else {
		client_cmd(0, "spk Gman/Gman_Choose%d", random_num(1, 2));
	}
}
public changeMapInFive(){
	isMapChanging = 1;
	set_hudmessage(255, 0, 0, -1.0, 0.17, 0, 6.0, 5.0);
	ShowSyncHudMsg(0, gHudCenteredMsg, "%L", LANG_PLAYER, "MAP_CHANGE");
	
	message_begin(MSG_ALL, SVC_INTERMISSION);
	message_end();
	set_cvar_num("mp_chattime", 5);
	countDown(5);
	set_task(5.0, "changeMap");
}
public changeMap(){
	isMapChanging =1;
	if(!gNextMap[0]){
		new mapId;
		new minP;
		new maxP;
		for(new i=0;i<gAllMapsCount;i++){
			if(getMapNameById(i, gNextMap)){
				if(isMapValid(gNextMap, 1, 1, 1, mapId, gNextMap, minP, maxP)){
					break;
				}
			}
		}
		if(!gNextMap[0])
			copy(gNextMap, MAP_NAME_LEN -1, "de_dust2");
	}
	server_cmd("changelevel %s", gNextMap);
}

stock isMapValid(const map[], checkMin, checkMax, checkLastPlayed, &mapId, mapRealName[MAP_NAME_LEN], &minP, &maxP){
	new mapInfo[13];
	new timestamp;
	new mapNo[4];
	new minPlayers[3];
	new maxPlayers[3];
	mapId = -1;
	
	if(nvault_lookup(gVault, map, mapInfo, 15, timestamp)) {
		copy(mapRealName, MAP_NAME_LEN -1, map);
		
		new curmap[MAP_NAME_LEN];
		get_mapname(curmap, MAP_NAME_LEN -1);
		if(equali(map, curmap))
			return MAP_CURRENT;
		if(checkLastPlayed){
			if(wasMapRecentlyPlayed(map, checkLastPlayed)){
				return MAP_LAST_PLAYED;
			}
		}
		
		new playersNum = getRealPlayersnum();
		parse(mapInfo, mapNo, charsmax(mapNo), minPlayers, charsmax(minPlayers), maxPlayers, charsmax(maxPlayers));
		minP = str_to_num(minPlayers);
		maxP = str_to_num(maxPlayers);
		if(checkMin && minP > 0 && playersNum < minP)
			return MAP_NOT_ENOUGH_PLAYERS;

		if(checkMax && maxP > 0 && playersNum > maxP)
			return MAP_TOO_MANY_PLAYERS;
		
		mapId = str_to_num(mapNo);
		return MAP_VALID_FOR_NOMINATION;
	} else if(!isMapWithPrefix(map)) {
		new tmp[MAP_NAME_LEN];
		new valid;
		for(new i =0;i< gMapPrefixCount; i++){
			formatex(tmp, MAP_NAME_LEN -1, "%s%s", gMapPrefix[i], map);
			valid = isMapValid(tmp, checkMin, checkMax, checkLastPlayed, mapId, mapRealName, minP, maxP);
			if(valid != MAP_NOT_PRESENT)
				return valid;
		}
	}
	mapRealName[0] = 0;
	return MAP_NOT_PRESENT;
}

stock isMapWithPrefix(const map[]){
	for(new i =0;i< gMapPrefixCount; i++){
		if(strfind(map, gMapPrefix[i]) == 0)
			return 1;
	}
	return 0;
}

stock wasMapRecentlyPlayed(const mapName[], count) {
	if(count){
		new key[16];
		new map[MAP_NAME_LEN];
		new timestamp;
		for(new i=0;i<count;i++){
			formatex(key, 15, "%s%d", keyLastPlayed, i);
			if(nvault_lookup(gVault, key, map, MAP_NAME_LEN -1, timestamp)){
				if(equal(map, mapName))
					return 1;
			}
			else {
				return 0;
			}
		}
	}
	return 0;
}

stock getRealPlayersnum(){
	new players[32];
	new num;
	get_players(players, num, "ch");
	return num;
}

stock getMapNameById(mapId, mapName[MAP_NAME_LEN]){
	new mapIdS[4];
	new timestamp;
	num_to_str(mapId, mapIdS, charsmax(mapIdS));
	return nvault_lookup(gVault, mapIdS, mapName, MAP_NAME_LEN -1, timestamp);
}
stock bool:isLastRound(){
	new Float:roundTime = getRoundTimeSeconds();
	new Float:mapEndsIn = getSecondsToMapEnd();	
	if(roundTime > mapEndsIn)
		return true;
	return false;
}
stock Float:getSecondsToMapEnd(){
	return get_pcvar_float(gCvarMP_TIMELIMIT) * 60.0 - get_gametime();
}
stock Float:getSecondsToRoundEnd(){
	return get_pcvar_float(gCvarMP_ROUNDTIME) * 60.0 -  getRoundSeconds();
}
stock Float:getRoundSeconds(){
	return get_gametime() - roundStartTime;
}
stock Float:getRoundTimeSeconds(){
	return get_pcvar_float(gCvarMP_ROUNDTIME) * 60.0;
}

stock getTop2(const tab[], const tabCount[], count, &itm1, &idx1,  &itm1count, &itm2, &idx2,  &itm2count) {
	for(new i=0;i<count;i++){
		if(tabCount[i] > itm1count){
			itm2count = itm1count;
			itm2 = itm1;
			idx2 = idx1;
			itm1 = tab[i];
			idx1 = i;
			itm1count = tabCount[i];
		} else {
			if(tabCount[i] > itm2count){
				itm2 = tab[i];
				idx2 = i;
				itm2count = tabCount[i];
			}
		}
	}
}
